#include "MyGlWindow.h"
#include <QOpenGLExtraFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions_3_3_Core>
#include <QImage>
#include <QOpenGLTexture>
#include <QDebug>


static GLfloat  vertices[] = {
    // positions        // texture coords
     0.5f,  0.5f, 0.0f, 1.0f, 1.0f, // top right
     0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right
    -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left
    -0.5f,  0.5f, 0.0f, 0.0f, 1.0f  // top left
};

static unsigned int indices[] = { // note that we start from 0!
    0, 1, 3, // first triangle
    1, 2, 3 // second triangle
};


MyGlWindow::MyGlWindow()
    : MY_GL_FUNC(nullptr)
    , program(nullptr)
    , vao(nullptr)
    , vbo(nullptr)
    , ebo(nullptr)
    , texture1(nullptr)
    , texture2(nullptr)
{}

void MyGlWindow::initializeGL()
{
    MY_GL_FUNC = context()->versionFunctions<QOpenGLFunctions_3_3_Core>();

    if (! MY_GL_FUNC) {
        qCritical("NO support for QOpenGLFunctions_3_3_Core!");
        ::exit(0);
    }

    connect(context(), SIGNAL(aboutToBeDestroyed()), SLOT(teardownGL()));
    MY_GL_FUNC->initializeOpenGLFunctions();
    MY_GL_FUNC->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    bool rc = false;

    // create vertex buffer objects (VBO)
    vbo = new QOpenGLBuffer;
    rc = vbo->create();
    Q_ASSERT(rc);

    rc = vbo->bind();
    Q_ASSERT(rc);

    //vbo->setUsagePattern(QOpenGLBuffer::StaticDraw);
    qDebug() << Q_FUNC_INFO << "VBO usage pattern" << vbo->usagePattern();
    vbo->allocate(vertices, sizeof(vertices));

    // create VAO
    vao = new QOpenGLVertexArrayObject(this);
    rc = vao->create();
    Q_ASSERT(rc);

    vao->bind();

    // create element buffer objects (EBO)
    ebo = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    rc = ebo->create();
    Q_ASSERT(rc);

    rc = ebo->bind();
    Q_ASSERT(rc);

    //ebo->setUsagePattern(QOpenGLBuffer::StaticDraw);
    qDebug() << Q_FUNC_INFO << "EBO usage pattern" << ebo->usagePattern();
    ebo->allocate(indices, sizeof(indices));

    // load texture
    QImage img1(":/texture/container.jpg"), img2(":/texture/awesomeface.png");
    Q_ASSERT((! img1.isNull()) && (! img2.isNull()));

    texture1 = new QOpenGLTexture(img1.mirrored());
    texture1->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    texture1->setMagnificationFilter(QOpenGLTexture::Linear);
    texture2 = new QOpenGLTexture(img2.mirrored());
    texture2->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
    texture2->setMagnificationFilter(QOpenGLTexture::Linear);

    // create program
    program = new QOpenGLShaderProgram(this);

    rc = program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/simple.vert");
    Q_ASSERT(rc);

    rc = program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/simple.frag");
    Q_ASSERT(rc);

    rc = program->link();
    Q_ASSERT(rc);

    rc = program->bind();
    Q_ASSERT(rc);

    program->enableAttributeArray(0);
    program->setAttributeBuffer(0, GL_FLOAT, 0, 3, 5 * sizeof(GLfloat));
    program->enableAttributeArray(1);
    program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));

    // assign texture units
    program->setUniformValue("texture1", 0);
    program->setUniformValue("texture2", 1);

    // Release (unbind) all
    program->release();
    texture1->release();
    texture2->release();

    // do NOT unbind the EBO while a VAO is active as the bound element buffer object IS stored in the VAO; keep the EBO bound.
    // ebo->release();

    vao->release();
    ebo->release();
    vbo->release();
    printContextInformation();
}

void MyGlWindow::paintGL()
{
    MY_GL_FUNC->glClear(GL_COLOR_BUFFER_BIT);
    bool rc = false;

    rc = program->bind();
    Q_ASSERT(rc);

    texture1->bind(0);
    texture2->bind(1);
    vao->bind();

    MY_GL_FUNC->glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

    program->release();
    texture1->release();
    texture2->release();
    vao->release();
    ebo->release();
    vbo->release();
}

void MyGlWindow::teardownGL()
{
    delete program;
    program = nullptr;

    delete texture1;
    texture1 = nullptr;

    delete texture2;
    texture2 = nullptr;

    delete vao;
    vao = nullptr;

    delete ebo;
    ebo = nullptr;

    delete vbo;
    vbo = nullptr;
}

void MyGlWindow::printContextInformation()
{
  QString glType;
  QString glVersion;
  QString glProfile;

  // Get Version Information
  glType = (context()->isOpenGLES()) ? "OpenGL ES" : "OpenGL";
  glVersion = reinterpret_cast<const char*>(MY_GL_FUNC->glGetString(GL_VERSION));
//  glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));

  // Get Profile Information
#define CASE(c) case QSurfaceFormat::c: glProfile = #c; break
  switch (format().profile())
  {
    CASE(NoProfile);
    CASE(CoreProfile);
    CASE(CompatibilityProfile);
  }
#undef CASE

  // qPrintable() will print our QString w/o quotes around it.
  qDebug() << qPrintable(glType) << qPrintable(glVersion) << "(" << qPrintable(glProfile) << ")";
}
